﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using PloobsEngine.Utils;
using PloobsEngine.Modelo;
using PloobsEngine.SceneControl;
using PloobsEngine.Cameras;
using PloobsEngine.Modelo.Animation;

namespace PloobsEngine.Material
{
    public class InstancingStaticNormalDeferred : IShader
    {
        private Effect _shader;        
        VertexDeclaration vertexDeclaration;
        VertexElement[] streamElements;
        int count = 10;
        VertexBuffer instanceBuffer;
        bool setUpTransforms;
        InstanceTechnich tech = InstanceTechnich.COLOR;
        InstanceInfo[] instances;

        struct InstanceInfo
        {
            public Matrix World;
            public Vector4 Color;
        };


        public InstancingStaticNormalDeferred(int count, InstanceTechnich tech)
        {
            this.tech = tech;
            this.count = count;
            setUpTransforms = false;
        }

        public InstancingStaticNormalDeferred(int count, InstanceTechnich tech , Matrix[] matrices , Color[] colors)
        {
            if (matrices.Count() != count || (colors!=null && colors.Count() != count))
                throw new Exception("matrices/colors must be the same size of count");

            this.tech = tech;
            this.count = count;
            setUpTransforms = true;

            instances = new InstanceInfo[count];
            Random rnd = new Random();
            
            for (int i = 0; i < count; i++)
            {
                instances[i].World = matrices[i];
                if (colors == null)
                {
                    instances[i].Color = new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), 1);
                }
                else
                {
                    instances[i].Color = colors[i].ToVector4(); ;
                }
            }
        }        

        private float specularIntensity = 0f;    
        public float SpecularIntensity
        {
            get { return specularIntensity; }
            set { specularIntensity = value; }
        }
        private float specularPower = 0f;

        public float SpecularPower
        {
            get { return specularPower; }
            set { specularPower = value; }
        }
        
        public override void Draw(IModelo modelo, IRenderHelper render, ICamera camera)
        {
            if (obj != null)
            {
                EngineStuff.GraphicsDevice.VertexDeclaration = vertexDeclaration;
                this._shader.Parameters["Texture"].SetValue(modelo.getTexture(TextureType.DIFFUSE));
                this._shader.Parameters["id"].SetValue(shaderId);
                this._shader.Parameters["specularIntensity"].SetValue(specularIntensity);
                this._shader.Parameters["specularPower"].SetValue(specularPower);

                Matrix[] boneTransforms = modelo.getBonesTransformation();
                for (int i = 0; i < modelo.MeshNumber; i++)
                {
                    BatchInformation[] bi = modelo.GetBatchInformation(i);
                    this._shader.Parameters["WVP"].SetValue(boneTransforms[modelo.GetParentBoneIndex(i)] * camera.View * camera.Projection);
                    for (int j = 0; j < bi.Count(); j++)
                    {
                        this._shader.Begin();
                        this._shader.CurrentTechnique.Passes[0].Begin();
                        EngineStuff.GraphicsDevice.Vertices[0].SetSource(modelo.GetVertexBuffer(i), bi[j].StreamOffset, bi[j].VertexStride);
                        EngineStuff.GraphicsDevice.Indices = modelo.GetIndexBuffer(i);
                        EngineStuff.GraphicsDevice.Vertices[0].SetFrequencyOfIndexData(count);
                        EngineStuff.GraphicsDevice.Vertices[1].SetSource(instanceBuffer, 0, sizeof(float) * (16 + 4));
                        EngineStuff.GraphicsDevice.Vertices[1].SetFrequencyOfInstanceData(1);
                        EngineStuff.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, bi[j].BaseVertex, 0, bi[j].NumVertices, bi[j].StartIndex, bi[j].PrimitiveCount);
                        this._shader.CurrentTechnique.Passes[0].End();
                        this._shader.End();
                    }
                }
                EngineStuff.GraphicsDevice.Vertices[1].SetFrequencyOfInstanceData(1);
                EngineStuff.GraphicsDevice.Vertices[0].SetFrequencyOfIndexData(1);
            }
        }

        public override MaterialType MaterialType
        {
            get { return MaterialType.DEFERRED; }
        }

        public override void Initialize()
        {
            base.Initialize();
            this._shader = EngineStuff.InternalContentManager.GetAsset<Effect>("InstanceRenderGBuffer");
            switch (tech)
            {
                case InstanceTechnich.COLOR:
                    this._shader.CurrentTechnique = this._shader.Techniques["TechniqueColor"];
                    break;
                case InstanceTechnich.TEXTURE:
                    this._shader.CurrentTechnique = this._shader.Techniques["TechniqueTexture"];
                    break;
            }

        }

        public override void PreUpdate(IObject ent, IList<PloobsEngine.Light.ILight> lights)
        {
            base.PreUpdate(ent, lights);
            //GenerateBuffers(ent);
            GenerateStreamElements(ent);
            if (setUpTransforms == false)
            {
                GenerateInstanceInformation(ent);
            }
            else
            {
                instanceBuffer = new VertexBuffer(EngineStuff.GraphicsDevice, sizeof(float) * (16 + 4) * count, BufferUsage.WriteOnly);
                instanceBuffer.SetData(instances);
            }
        }

        //private void GenerateBuffers(IObject ent)
        //{
            //geometryBuffer = new VertexBuffer(EngineStuff.GraphicsDevice, typeof(VertexPositionNormalTexture), 6 * 4, BufferUsage.WriteOnly);

            //VertexPositionNormalTexture[] array = new VertexPositionNormalTexture[6 * 4];

            ////BACK FACE
            //array[0] = new VertexPositionNormalTexture(new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(0, 0, -1), new Vector2(1, 1));
            //array[1] = new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, -0.5f), new Vector3(0, 0, -1), new Vector2(0, 1));
            //array[2] = new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, -0.5f), new Vector3(0, 0, -1), new Vector2(1, 0));
            //array[3] = new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, -0.5f), new Vector3(0, 0, -1), new Vector2(0, 0));

            ////TOP FACE
            //array[4] = new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, -0.5f), new Vector3(0, 1, 0), new Vector2(0, 0));
            //array[5] = new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, -0.5f), new Vector3(0, 1, 0), new Vector2(1, 0));
            //array[6] = new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(0, 1, 0), new Vector2(0, 1));
            //array[7] = new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(0, 1, 0), new Vector2(1, 1));

            ////FRONT FACE
            //array[8] = new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0, 0));
            //array[9] = new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(1, 0));
            //array[10] = new VertexPositionNormalTexture(new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(0, 1));
            //array[11] = new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, 0.5f), new Vector3(0, 0, 1), new Vector2(1, 1));

            ////BOTTOM FACE
            //array[12] = new VertexPositionNormalTexture(new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(0, -1, 0), new Vector2(1, 1));
            //array[13] = new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, 0.5f), new Vector3(0, -1, 0), new Vector2(0, 1));
            //array[14] = new VertexPositionNormalTexture(new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(0, -1, 0), new Vector2(1, 0));
            //array[15] = new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, -0.5f), new Vector3(0, -1, 0), new Vector2(0, 0));

            ////LEFT FACE
            //array[16] = new VertexPositionNormalTexture(new Vector3(-0.5f, -0.5f, 0.5f), new Vector3(-1, 0, 0), new Vector2(1, 1));
            //array[17] = new VertexPositionNormalTexture(new Vector3(-0.5f, -0.5f, -0.5f), new Vector3(-1, 0, 0), new Vector2(0, 1));
            //array[18] = new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, 0.5f), new Vector3(-1, 0, 0), new Vector2(1, 0));
            //array[19] = new VertexPositionNormalTexture(new Vector3(-0.5f, 0.5f, -0.5f), new Vector3(-1, 0, 0), new Vector2(0, 0));

            ////RIGHT FACE
            //array[20] = new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(1, 1));
            //array[21] = new VertexPositionNormalTexture(new Vector3(0.5f, -0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0, 1));
            //array[22] = new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, -0.5f), new Vector3(1, 0, 0), new Vector2(1, 0));
            //array[23] = new VertexPositionNormalTexture(new Vector3(0.5f, 0.5f, 0.5f), new Vector3(1, 0, 0), new Vector2(0, 0));

            ////geometryBuffer.SetData<VertexPositionNormalTexture>(array);

          //  geometryBuffer = ent.Modelo.getModelMeshes(0)[0].VertexBuffer;


            //indexBuffer = new IndexBuffer(EngineStuff.GraphicsDevice, typeof(int), 36, BufferUsage.None);


            //int[] iarray = new int[36];
            //int addInd = 0;
            //int addVert = 0;

            ////For each face of the cube
            //for (int i = 0; i < 6; i++)
            //{
            //    //Index offset for each face of the cube.
            //    addInd = (i * 6);
            //    //Vertex offset for each face of the cube.
            //    addVert = (i * 4);

            //    //Store the indices for each vertex for every instance;
            //    iarray[0 + addInd] = (0 + addVert);
            //    iarray[1 + addInd] = (2 + addVert);
            //    iarray[2 + addInd] = (3 + addVert);
            //    iarray[3 + addInd] = (0 + addVert);
            //    iarray[4 + addInd] = (3 + addVert);
            //    iarray[5 + addInd] = (1 + addVert);
            //}

            ////indexBuffer.SetData<int>(iarray);

          //   indexBuffer = ent.Modelo.getModelMeshes(0)[0].IndexBuffer;

            
        //}

        private void GenerateStreamElements(IObject ent)
        {
            streamElements = new VertexElement[8];
            streamElements[0] = new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0);
            streamElements[1] = new VertexElement(0, 12, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0);
            streamElements[2] = new VertexElement(0, 24, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0);            

            streamElements[3] = new VertexElement(1, 0                , VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1);
            streamElements[4] = new VertexElement(1, sizeof(float) * 4, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 2);
            streamElements[5] = new VertexElement(1, sizeof(float) * 8, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 3);
            streamElements[6] = new VertexElement(1, sizeof(float) * 12, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate,4);
            streamElements[7] = new VertexElement(1, sizeof(float) * 16, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 5);
            vertexDeclaration = new VertexDeclaration(EngineStuff.GraphicsDevice, streamElements);
        }

        private void GenerateInstanceInformation(IObject ent)
        {
            InstanceInfo[] instances = new InstanceInfo[count];
            Random rnd = new Random();
            float dist = 150f;
            for (int i = 0; i < count; i++)
            {
                instances[i].World = Matrix.CreateTranslation(new Vector3(i * dist,i * dist, i * dist));
                instances[i].Color = new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), 1);
            }

            instanceBuffer = new VertexBuffer(EngineStuff.GraphicsDevice, sizeof(float) * (16 + 4) * count, BufferUsage.WriteOnly);
            instanceBuffer.SetData(instances);
        }
    }
}
